﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Xml.Linq;

using ProjectBase.Core;

namespace ProjectBase.Tools.Publishing.Handlers.ExceptionHandler
{
    public class ConfigurationReader
    {
        public virtual ExceptionConfiguration Create(string exceptionConfigPath)
        {
            Contract.Requires(exceptionConfigPath.IsNotEmpty(), "ExceptionConfiguration: The parameter 'exceptionConfigPath' cannot be null nor empty ");

            var configNode = LoadConfiguration(exceptionConfigPath);

            return Create(configNode);
        }

        public virtual ExceptionConfiguration Create(XElement configElement)
        {
            Contract.Requires(configElement.Is(), "ExceptionConfiguration: The parameter 'configElement' cannot be null");

            var configuration = new ExceptionConfiguration();

            if (configElement.Attribute(Constants.AttributeMode).Is()
             && !configElement.Attribute(Constants.AttributeMode)
                    .Value.IsEqual(Constants.AttributeModeValueOn))
            {
                configuration.DoNotPublish = true;
            }

            var exceptions = configElement.Element(Constants.ElementExceptions);
            if (exceptions.IsNull())
            {
                return configuration;
            }

            ParseExceptionNodes(configuration, exceptions);

            return configuration;
        }
        public virtual ExceptionConfiguration SetDefaults(XElement handlerSettings, ExceptionConfiguration configuration)
        {
            Contract.Requires(configuration.Is(), "ExceptionConfiguration: parameter 'configuration' cannot be null");
            Contract.Requires(handlerSettings.Is(), "ExceptionConfiguration: parameter 'handlerSettings' cannot be null");

            var defaultErrorCode = handlerSettings.Element(Constants.ElementDefaultErrorCode);
            if (defaultErrorCode.Is())
            {
                configuration.DefaultEventId = ResolveErrorCode(defaultErrorCode.Attribute(Constants.AttributeValue));
            }
            var defaultSeverity = handlerSettings.Element(Constants.ElementDefaultSeverity);
            if (defaultSeverity.Is())
            {
                configuration.DefaultEventLogEntryType = ResolveEntryType(defaultSeverity.Attribute(Constants.AttributeValue));
            }
            return configuration;
        }

        protected virtual void ParseExceptionNodes(ExceptionConfiguration configuration, XElement exceptions)
        {
            Contract.Requires(configuration.Is());
            Contract.Requires(exceptions.Is());

            foreach (var exeptionNode in exceptions.Elements(Constants.ElementException))
            {
                configuration.Exceptions.Add
                    (
                        new ExceptionSettings
                        {
                            EventId = ResolveErrorCode(exeptionNode.Attribute(Constants.AttributeEventId)),
                            ExceptionType = ResolveExceptionType(exeptionNode.Attribute(Constants.AttributeType)),
                            EventLogEntryType = ResolveEntryType(exeptionNode.Attribute(Constants.AttributeSeverity)),
                        }
                    );
            }
        }

        #region resolve exception element
        protected virtual Type ResolveExceptionType(XAttribute type)
        {
            Contract.Requires(type.Is(), "ExceptionConfiguration: exception element has missing attribute 'type'");
            return Type.GetType(type.Value);
        }

        protected virtual int ResolveErrorCode(XAttribute errorCode)
        {
            Contract.Requires(errorCode.Is(), "ExceptionConfiguration: exception element has missing attribute 'id'");
            try
            {
                return (int)errorCode;
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException("ExceptionConfiguration: exception element has incorrect attribute id: '{0}'"
                    .FormatWith(errorCode.Value), ex);
            }
        }

        protected virtual EventLogEntryType? ResolveEntryType(XAttribute severity)
        {
            if (severity.IsNull())
            {
                return EventLogEntryType.Error;
            }
            var level = severity.Value;
            if (level.IsEmpty())
            {
                return EventLogEntryType.Error;
            }
            switch (level.Substring(0, 1))
            {
                case "F": return EventLogEntryType.FailureAudit;
                case "I": return EventLogEntryType.Information;
                case "S": return EventLogEntryType.SuccessAudit;
                case "W": return EventLogEntryType.Warning;
                case "N": return null;
            }
            return EventLogEntryType.Error;
        }
        #endregion resolve exception element

        protected virtual XElement LoadConfiguration(string exceptionConfigPath)
        {
            Contract.Requires(exceptionConfigPath.IsNotEmpty(), "ExceptionConfiguration: The parameter 'exceptionConfigPath' cannot be null nor empty ");

            var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, exceptionConfigPath);
            XDocument document;

            try
            {
                document = XDocument.Load(path);
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException("ExceptionConfiguration: The provided file '{0}' either does not exists, or cannot be loaded (full path:'{1}')"
                    .FormatWith(exceptionConfigPath, path), ex);
            }

            return document.Root.Element(Constants.ElementExceptionManagement);
        }
    }
}